package cn.lingyangwl.framework.data.mate.fieldbind;

import cn.hutool.core.util.StrUtil;
import cn.lingyangwl.framework.data.mate.exception.FieldBindException;
import cn.lingyangwl.framework.data.mate.fieldbind.inter.IDataBind;
import cn.lingyangwl.framework.data.mate.fieldbind.inter.IFieldBindAnnotationDataGetter;
import cn.lingyangwl.framework.data.mate.fieldbind.model.AnnotationMetadata;
import cn.lingyangwl.framework.data.mate.fieldbind.model.DictClassCache;
import cn.lingyangwl.framework.data.mate.fieldbind.model.DictType;
import cn.lingyangwl.framework.data.mate.fieldbind.model.FieldDefine;
import cn.lingyangwl.framework.tool.core.ObjectUtils;
import cn.lingyangwl.framework.tool.core.StringUtils;
import cn.lingyangwl.framework.tool.core.exception.BizException;
import cn.lingyangwl.framework.tool.core.reflection.DefaultReflectorFactory;
import cn.lingyangwl.framework.tool.core.reflection.MetaObject;
import cn.lingyangwl.framework.tool.core.reflection.factory.DefaultObjectFactory;
import cn.lingyangwl.framework.tool.core.reflection.wrapper.DefaultObjectWrapperFactory;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author shenguangyang
 */
@Component
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public class FieldBindHandler implements ApplicationContextAware {
    private static final Logger log = LoggerFactory.getLogger(FieldBindHandler.class);
    private IDataBind<Object> dataBind;

    @Resource
    private Map<String, IFieldBindAnnotationDataGetter> fieldBindAnnotationDataGetterMap;
    @Resource
    private FieldValueManager fieldValueManager;

    private static final Map<Class<?>, DictClassCache> CACHE = new ConcurrentHashMap<>();
    private static final DefaultObjectFactory defaultObjectFactory = new DefaultObjectFactory();
    private static final DefaultObjectWrapperFactory defaultObjectWrapperFactory = new DefaultObjectWrapperFactory();
    private static final DefaultReflectorFactory defaultReflectorFactory = new DefaultReflectorFactory();

    @PostConstruct
    public void init() {

    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, IDataBind> map = applicationContext.getBeansOfType(IDataBind.class);
        if (map.size() > 2) {
            throw new RuntimeException("there can only be one " + IDataBind.class.getSimpleName() + " subclass in a module");
        }
        if (CollectionUtils.isEmpty(map)) {
            throw new BizException("没有实现 {} 数据绑定子类", IDataBind.class.getName());
        }
        for (Map.Entry<String, IDataBind> entry : map.entrySet()) {
            dataBind = entry.getValue();
            log.warn("use [{}] complete data binding", entry.getValue().getClass().getName());
        }
    }

    public DictClassCache createDictClassCache(Object targetObj) {
        if (Objects.isNull(targetObj)) {
            return null;
        }
        Class<?> targetClass = targetObj.getClass();
        DictClassCache dictClassCache = CACHE.get(targetClass);
        if (Objects.nonNull(dictClassCache)) {
            return dictClassCache;
        }
        synchronized (targetObj.getClass().getName().intern()) {
            dictClassCache = CACHE.get(targetClass);
            if (Objects.nonNull(dictClassCache)) {
                return dictClassCache;
            }

            DictClassCache newDictClassCache = new DictClassCache();
            newDictClassCache.setClazz(targetClass);
            List<Field> allFields = ObjectUtils.getFieldsWithSuper(targetObj);

            fieldBindAnnotationDataGetterMap.forEach((eKey, fieldAnnotationDataGetter) -> {
                Class<? extends Annotation> FIELD_ANNO = (Class<? extends Annotation>) ((ParameterizedType) AopUtils.getTargetClass(fieldAnnotationDataGetter)
                        .getGenericInterfaces()[0])
                        .getActualTypeArguments()[0];

                allFields.forEach(f -> {
                    f.setAccessible(true);
                    Annotation annotation = f.getAnnotation(FIELD_ANNO);
                    if (Objects.isNull(annotation)) {
                        return;
                    }

                    AnnotationMetadata anno = fieldAnnotationDataGetter.initFieldBindAnnotation(annotation);
                    if (Objects.isNull(anno)) {
                        return;
                    }
                    FieldDefine fieldDefine = new FieldDefine();
                    fieldDefine.setField(f);
                    fieldDefine.setAnnotationMetadata(anno);
                    try {
                        fieldDefine.setTargetField(f.getDeclaringClass().getDeclaredField(anno.getTarget()));
                        if (!StringUtils.isAnyEmpty(anno.getMapperTarget(), anno.getCodeDefault())) {
                            fieldDefine.setMapperField(f.getDeclaringClass().getDeclaredField(anno.getMapperTarget()));
                        }
                        newDictClassCache.getFieldList().add(fieldDefine);
                    } catch (Exception e1) {
                        log.error("error: ", e1);
                        throw new BizException("字段翻译异常");
                    }
                });
                newDictClassCache.fieldAnnotationTypes();
            });
            CACHE.put(targetClass, newDictClassCache);
            return newDictClassCache;
        }
    }

    public void handleField(Object obj) {
        Map<Object, Class<?>> map = new ConcurrentHashMap<>();
        // key是type
        Map<String, DictType> allTypes = new ConcurrentHashMap<>();
        List<ObjectInfo> allObjectInfo = new CopyOnWriteArrayList<>();

        handleField(obj, map, allTypes, allObjectInfo);
        List<DictType> dictTypeList = new ArrayList<>(allTypes.values());
        Object valuesOfTypes = dataBind.getCodesOfTypes(dictTypeList);
        allObjectInfo.stream().parallel().forEach(info -> {
            MetaObject metaObject = MetaObject.forObject(info.getObject(), defaultObjectFactory, defaultObjectWrapperFactory, defaultReflectorFactory);
            invokeDataBind(info.getObject(), metaObject, info.getClassInfo(), valuesOfTypes);
        });
    }

    /**
     * 完成收集字段
     */
    private void handleField(Object obj, Map<Object, Class<?>> map, Map<String, DictType> allTypes,
                             List<ObjectInfo> allObjectInfo) {
        if (Objects.isNull(obj)) {
            return;
        }

        if (obj instanceof Collection) {
            handleCollectionField((Collection<Object>) obj, map, allTypes, allObjectInfo);
        } else if (obj.getClass().isArray()) {
            handleArrayField(obj, map, allTypes, allObjectInfo);
        } else if (obj instanceof Map) {
            handleMapField((Map<Object, Object>) obj, map, allTypes, allObjectInfo);
        } else if (!isJavaClass(obj.getClass())) {
            handleSingleField(obj, map, allTypes, allObjectInfo);
        }
    }

    private void handleMapField(Map<Object, Object> objMap, Map<Object, Class<?>> map,
                                Map<String, DictType> allTypes, List<ObjectInfo> allObjectInfo) {
        if (Objects.isNull(objMap)) {
            return;
        }
        objMap.forEach((key, value) -> handleField(value, map, allTypes, allObjectInfo));
    }

    /**
     * 完成对象中字段绑定
     */
    private void handleObjectField(Object obj, Map<Object, Class<?>> map, Map<String, DictType> allTypes,
                                   List<ObjectInfo> allObjectInfo) {
        if (Objects.isNull(obj)) {
            return;
        }
        List<Field> fields = ObjectUtils.getFieldsWithSuper(obj);
        for (Field field : fields) {
            try {
                int modifiers = field.getModifiers();
                if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)
                        || Modifier.isInterface(modifiers) || Modifier.isAbstract(modifiers)) {
                    continue;
                }
                field.setAccessible(true);
                Object o = field.get(obj);
                handleField(o, map, allTypes, allObjectInfo);
            } catch (Exception e) {
                log.error("error: ", e);
            }
        }
    }

    /**
     * 完成单字段绑定, eg:
     * class Test {
     *  String name;
     *  Long age;
     * }
     */
    private void handleSingleField(Object obj, Map<Object, Class<?>> map, Map<String, DictType> allTypes,
                                   List<ObjectInfo> allObjectInfo) {
        DictClassCache dictClassCache = CACHE.get(obj.getClass());
        if (Objects.isNull(dictClassCache)) {
            dictClassCache = createDictClassCache(obj);
            if (Objects.isNull(dictClassCache)) {
                return;
            }
        }

        int hashCode = System.identityHashCode(obj);
        if (map.containsKey(hashCode)) {
            return;
        }
        map.put(hashCode, obj.getClass());
        handleObjectField(obj, map, allTypes, allObjectInfo);
        dictClassCache.getFieldList()
                .forEach(fieldDefine -> {
                    String type = fieldDefine.getAnnotationMetadata().getType();
                    DictType dictType = allTypes.computeIfAbsent(type, x -> new DictType());
                    dictType.setType(type);
                    dictType.setFieldValues(getDictFieldValues(obj, dictType.getFieldValues(), fieldDefine));
                });
        allObjectInfo.add(new ObjectInfo(obj, dictClassCache));
    }

    private void handleArrayField(Object obj, Map<Object, Class<?>> map,
                                  Map<String, DictType> allTypes, List<ObjectInfo> allObjectInfo) {
        int hashCode = System.identityHashCode(obj);
        if (Objects.isNull(obj) || map.containsKey(hashCode)) {
            return;
        }
        map.put(hashCode, obj.getClass());
        int len = Array.getLength(obj);
        if (len == 0) {
            return;
        }
        for (int i = 0; i < len; i++) {
            handleField(Array.get(obj, i), map, allTypes, allObjectInfo);
        }
    }

    /**
     * 对集合中的每个对象做字段绑定处理
     */
    private void handleCollectionField(Collection<Object> objs, Map<Object, Class<?>> map,
                                       Map<String, DictType> allTypes, List<ObjectInfo> allObjectInfo) {
        int hashCode = System.identityHashCode(objs);
        if (CollectionUtils.isEmpty(objs) || map.containsKey(hashCode)) {
            return;
        }
        map.put(hashCode, objs.getClass());
        objs.stream().findFirst()
                .ifPresent(obj -> objs.parallelStream().forEach(o -> handleField(o, map, allTypes, allObjectInfo)));
    }

    /**
     * 调度数据绑定接口
     *
     * @param metaObject     对象元数据
     * @param dictClassCache 目标类信息缓存
     * @param valuesOfTypes  目标类中所有types对应的所有values, 一般情况下是一个map集合
     */
    private void invokeDataBind(Object obj, MetaObject metaObject, DictClassCache dictClassCache, Object valuesOfTypes) {
        if (Objects.isNull(obj)) {
            return;
        }
        List<FieldDefine> fieldList = dictClassCache.getFieldList();
        fieldList.stream().parallel().forEach(fieldDefine -> {
            try {
                Object fieldValue = fieldDefine.getField().get(obj);
                fieldValueManager.setTargetValue(metaObject, valuesOfTypes, dataBind, fieldValue, fieldDefine);
            } catch (Exception e) {
                log.error("error: ", e);
            }
        });
    }

    private static boolean isJavaClass(Class<?> clz) {
        return clz.getClassLoader() == null;
    }

    /**
     * 获取字段值添加到 fieldValues 集合中
     */
    private Set<Object> getDictFieldValues(Object obj, Set<Object> fieldValues,
                                           FieldDefine fieldDefine) {
        if (Objects.isNull(fieldDefine)) {
            return fieldValues;
        }
        String delimiter = fieldDefine.getAnnotationMetadata().getDelimiter();
        Field field = fieldDefine.getField();
        field.setAccessible(true);
        try {
            Object value = field.get(obj);
            if (Objects.isNull(value)) {
                return fieldValues;
            }
            if ((value instanceof String) && StringUtils.isNotEmpty(delimiter)) {
                String fieldValue = (String) value;
                fieldValues.addAll(StrUtil.split(fieldValue, delimiter));
            } else {
                fieldValues.add(value);
            }
        } catch (IllegalAccessException e) {
            log.error("field bind fail, get field value error: {}", e.getMessage());
            throw new FieldBindException("字典翻译失败");
        }
        return fieldValues;
    }


    @Getter
    @Setter
    @AllArgsConstructor
    private static class ObjectInfo {
        private Object object;
        private DictClassCache classInfo;
    }
}
